package com.wframe.project.former.task;

import org.apache.commons.lang.math.RandomUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;

import java.lang.reflect.Method;

/**
 * Created by lwb on 2017/5/19.
 */
@Aspect
@Component
public class RedisLockAspect {
    private static final Logger log = LoggerFactory.getLogger(RedisLockAspect.class);

    @Autowired
    private RedisTemplate redisTemplate;

    @Pointcut("execution(* com.wframe.project.former.task.*Task.exectask(..)) && @annotation(com.wframe.project.former.task.RedisLock)")
    private void lockPoint() {
    }

    @Around("lockPoint()")
    public Object arround(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method method = methodSignature.getMethod();
        RedisLock lockInfo = method.getAnnotation(RedisLock.class);

        //线程休眠随机时间(0~5s)
        Thread.currentThread().sleep(RandomUtils.nextInt(5000));

        //取得当前时间
        long startTime = System.currentTimeMillis();
        //循环获取锁的等待超时时间，在此时间内会一直尝试获取锁直到超时，为0表示失败后直接返回不等待
        long maxTimeoutMills = lockInfo.maxTimeoutMills();
        //锁的最大生存时间
        long expireMills = lockInfo.expireMills();
        //锁的最大生存时刻
        long expire = startTime + expireMills;
        //睡眠时间
        long sleepMills = lockInfo.sleepMills();
        //key
        String redisKey = "Lock:" + lockInfo.value();

        Boolean lock = false;
        Object obj = null;
        int tryCount = 0;
        while (!lock) {
            tryCount++;
            lock = (Boolean) redisTemplate.execute(new RedisCallback<Boolean>() {
                @Override
                public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
                    Jedis jedis = (Jedis) connection.getNativeConnection();
                    String result = jedis.set(redisKey, String.valueOf(expire), "nx", "px", expireMills);
                    return "ok".equalsIgnoreCase(result);
                }
            }, true);

            long endTime = System.currentTimeMillis();
            long costTime = endTime - startTime;
            if (lock) {
                log.info("get redis global lock success,expireMills=" + expireMills + ",costTimeMillis=" + costTime
                        + ",key=" + redisKey + ",retryCount=" + tryCount + ",sleepTimeMillis=" + sleepMills
                        + ",costTimePerTime=" + costTime / ((double) tryCount) + ",startTime=" + startTime + ",endTime="
                        + endTime);
                //得到锁,执行方法，释放锁
                obj = pjp.proceed();
                delete(redisKey);
            } else if (lock == null || !lock) {
                if (lockInfo.action().equals(RedisLock.LockFailAction.CONTINUE)) {
                    // 加锁失败,阻塞调用线程
                    Thread.currentThread().sleep(sleepMills);
                } else {
                    // 加锁失败,非阻塞
                    break;
                }
                endTime = System.currentTimeMillis();
                costTime = endTime - startTime;
                //超时
                // 分布式自旋等待时间已经已经超过了某个时间,说明分布式竞争失败或者key没有正确的被设置超时时间.
                if (maxTimeoutMills <= 0 || costTime > maxTimeoutMills) {
                    log.info("get redis global lock fail,expireMills=" + expireMills + ",costTimeMillis=" + costTime
                            + ",key=" + redisKey + ",retryCount=" + tryCount + ",sleepTimeMillis=" + sleepMills
                            + ",costTimePerTime=" + costTime / ((double) tryCount) + ",startTime=" + startTime + ",endTime="
                            + endTime);
                    break;
                }
            }
        }
        return obj;
    }

    private void delete(String key) {
        long startTime = System.currentTimeMillis();
        redisTemplate.delete(key);
        long endTime = System.currentTimeMillis();
        log.info(" global unlock,key=" + key + " ,costTime millis="
                + (endTime - startTime) + ",startTime=" + startTime + ",endTime=" + endTime);
    }

}
